{\rtf1\ansi\ansicpg1252\uc1 \deff0\deflang1033\deflangfe1033{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;}}{\colortbl;\red0\green0\blue0;
\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;
\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{\widctlpar\adjustright \fs20\cgrid \snext0 Normal;}{\*\cs10 \additive Default Paragraph Font;}}{\*\listtable{\list\listtemplateid67698703
\listsimple{\listlevel\levelnfc0\leveljc0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\fi-360\li360\jclisttab\tx360 }{\listname ;}\listid59181661}}{\*\listoverridetable{\listoverride\listid59181661
\listoverridecount0\ls1}}{\info{\author George H Harth, IV}{\operator George H Harth, IV}{\creatim\yr1998\mo12\dy15\hr15\min13}{\revtim\yr1998\mo12\dy16\hr10\min45}{\version12}{\edmins36}{\nofpages8}{\nofwords2585}{\nofchars14738}
{\*\company Micron Electronics, Inc.}{\nofcharsws0}{\vern89}}\margl1440\margr1440 \widowctrl\ftnbj\aenddoc\hyphcaps0\viewkind1\viewscale100 \fet0\sectd \linex0\sectdefaultcl {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2
\pnucltr\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6
\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang
{\pntxtb (}{\pntxta )}}\pard\plain \nowidctlpar\adjustright \fs20\cgrid {\b\fs28\cf1\cgrid0 HOWTO: DialectLib Core Modules
\par }{\cf6\cgrid0 Last modified 16 Dec 1998}{\b\cf6\cgrid0 
\par 
\par }{\cgrid0 Any project that includes DialectLib should have one of the following values defined in the preprocessor definitions:
\par }\pard \fi720\nowidctlpar\adjustright {\cgrid0 __WINNT__ \tab - for Windows 95/98/NT
\par __WINCE__ \tab - for Windows CE
\par __UNIX__ \tab - for any Unix variant
\par }\pard \nowidctlpar\adjustright {\b\cf6\cgrid0 
\par }{\b\fs24\cf6\cgrid0 Two ways to register C++ core modules...}{\b\cf6\cgrid0  
\par (examples at end of doc)}{\cgrid0 
\par 
\par There two ways to tell the interpreter you have a core module compiled in.  In both methods, a script must use the import statement to load the module into it's local symbol table.
\par 
\par 1.\tab Override the virtual CInterpreter function }{\cf2\cgrid0 RequestModule}{\cgrid0 
\par \tab which is called by the interpreter when an import statement
\par \tab occurs.  If you support the requested module, return a frame
\par \tab that represents the module.  If you don't support the requested
\par \tab module, you must invoke the parent class's (usually CInterpreter)
\par \tab version of the RequestModule function so it can see if a module
\par \tab was registered using method number 2.
\par 
\par 2.\tab It is also possible to register a core module without
\par \tab overridding the RequestModule virtual function.  CInterpreter
\par \tab now has a function called }{\cf2\cgrid0 RegisterCoreModule }{\cgrid0 which takes two
\par \tab arguments.  The first is the module's name and the second
\par \tab is a pointer to a C function which, when invoked, returns the
\par \tab module's frame.  RegisterCoreModule is a public function so
\par \tab it's possible to add modules to an interpreter at any time
\par \tab during the life of the interpreter object.
\par 
\par A module frame is just an ordinary frame that will be used as the symbol table for that module.  Currently the only extra criteria is that when created, it must contain a slot named }{\b\cgrid0 __module__ }{\cgrid0 
which is set to the name of the module as requested in RequestModule. }{\b\cgrid0 Also, if you add a class to a module frame, be sure that you use the exact same name you used in the call to AllocClass().
\par }{\cgrid0 
\par }{\b\fs24\cf6\cgrid0 A few useful functions in building modules...
\par }{\cgrid0 
\par }{\f2\cf2\cgrid0 void CInterpreter::SyntaxError(LPCTSTR parmString, ...);
\par void CInterpreter::RuntimeError(LPCTSTR parmString, ...);
\par void CInterpreter::InternalError(LPCTSTR parmString, ...);
\par 
\par }{\cgrid0 All the above set Dialect style exceptions with the message being set to whatever the parmString and optional args
\par evaluate to (using printf style format speci
fiers).  In general, if you call one of these functions, you should return from the current function immediately without further processing. If you are in a propery writeFunction, you should return the orignal value of the property (args[0], as explained 
below). All core functions receive a pointer to the interpter that called them which can be used that pointer to invoke the exception.
\par 
\par }{\f2\cf2\cgrid0 VALUE AllocClass(TString className, VALUE baseClass);
\par 
\par }{\cgrid0 Returns a VALUE of type stClass.  The baseClass argument should ei
ther be undefined (VALUE()) or the result of another call to AllocClass().  To add member functions, values, properties, etc. to the class VALUE, use VALUE::Set() as you would on a frame value.  Any value added to a class is a shared value (like static cl
ass members in C++).
\par 
\par }{\f2\cf2\cgrid0 VALUE AllocFunction(VALUE (*lpCoreFunc)(VALUE* self, int argCount, 
\par \tab VALUE argVals[], CInterpreter* pInterp), TString moduleName);
\par 
\par }{\cgrid0 Which isn't as complicated as it first appears.  AllocFuntion takes one argument, which is the pointer 
to a function that takes four arguments as specified.  The self value is only valid when the function was a member of a class instance.  In which case, self is the instances "this" symbol table.  argCount contains the number of values in the args array.  
And, pInterp is a pointer to the interpreter making the function call.  The string }{\i\cgrid0 moduleName}{\cgrid0 
 is used to tell the interpreter which module the function considers it's home.  That is, when this function is executing, the interpreter will place the module named by }{\i\cgrid0 moduleName}{\cgrid0 
 on the symbol table stack.  A core function can use  the function }{\f2\cgrid0 CInterpreter::CurrentModuleFrame()}{\cgrid0 to get access to it's parent frame.
\par 
\par }{\f2\cf2\cgrid0 VALUE AllocProperty(VALUE readFunc, VALUE writeFunc, VALUE vInitial);
\par 
\par }{\cgrid0 Returns a VALUE of type stProperty.  Where readFunc and writeFunc are VALUEs of type stFunction created using AllocFunction.  And vInitial is the initial value assigned to the property.  If readFunc is undefined,
\par the property will simply return the value when requested.  If the writeFunc is undefined, the property will immediately accept the new value.  Therefore to make a read only or write only property, just make a function that
\par ignores the request, throws and exception, or whatever is appropriate.
\par 
\par And, of course all the other AllocXXX functions are useful. :)
\par 
\par 
\par }{\b\fs24\cf6\cgrid0 Quick info on core functions...
\par }{\b\cf6\cgrid0 
\par }{\cgrid0 They must have the following prototype and be non-class members or static class members:
\par }{\f2\cf2\cgrid0 
\par VALUE myFunction(VALUE* self, int argCount, VALUE args[], 
\par \tab CInterpreter* pInterp);
\par }{\cgrid0 
\par Where argCount == the number of arguments in the args array.  self is the "this" symbol table if the function belongs to a class instance.  pInterp points to the interpreter that invoked the function.
\par 
\par If the function needs to access self (i.e. it is a cla
ss member function), then it should first make sure the self does not equal NULL.  If self equals NULL, it is probably because the script tried to call the function directly from the class instead of a class instance.  It is recommended that the very firs
t line in a class member function that uses self call the }{\f2\cf2\cgrid0 ASSERT_SELF(moduleName)}{\cgrid0 
 macro defined in CInterpreter.h.  ASSERT_SELF compares self to NULL.  If self is NULL, then the macro sets an exception in the interpreter and returns a }{\b\cgrid0 nil}{\cgrid0  VALUE.  The argume
nt to ASSERT_SELF is the name of the module and will be used in the exception message.
\par 
\par Create a core function VALUE by calling :
\par }{\f2\cf2\cgrid0 
\par VALUE AllocFunction(VALUE (*lpCoreFunc)(VALUE* self, int argCount,
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cf2\cgrid0 VALUE argVals[], CInterpreter* pInterp), TString moduleName);
\par }\pard \nowidctlpar\adjustright {\cgrid0 
\par Where lpCoreFunc is the C function and moduleName is the name of the module used to import the function.  The name must be identical to the module name in order for  }{\f2\cgrid0 CInterpreter::CurrentModuleFrame()}{\cgrid0  to work.
\par 
\par It is possible to get the current module frame during the invocation of a core function by calling the following Cinterpreter function:
\par }{\cf2\cgrid0 
\par VALUE CurrentModuleFrame();
\par }{\cgrid0 
\par The return VALUE the frame in which the function was declared.  If the interpreter isn't actually executing, or for any other abnormal circumstance, the result is an empty frame VALUE.
\par 
\par 
\par }{\b\fs24\cf6\cgrid0 Quick info on executing other functions from inside a core function...}{\cgrid0 
\par 
\par If the functi
on you are executing is just another core function contained within the same class, and therefore the self pointer is the same, you can just invoke the core function directly.  Passing the self and pInterp parameters that were received in the original cor
e function and setting argCount and args to whatever makes sense.  If the core function is not from the same class, you should pass NULL in for the self argument.
\par 
\par If the function to execute was extracted from the self pointer, then call the following function to invoke it.
\par 
\par }{\f2\cf2\cgrid0 bool CInterpreter::ExecFuncFromCore(VALUE& result, VALUE vFunc, 
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cf2\cgrid0 int argCount, VALUE args[], VALUE* self);
\par }\pard \nowidctlpar\adjustright {\cgrid0 
\par ExecFuncFromCore returns false if an exception occurred during evaluation, in which case you should return immediately from the c
ore function.  If ExecFuncFromCore returns true, the result will contain the functions return value.  The self pointer given to ExecFuncFromCore should be the same one passed into the core function (which should be the same place the function was extracte
d from).  If the function VALUE to be executed was not extracted from the self pointer, then pass NULL in for self in ExecFuncFromCore.  Finally, vFunc is the function VALUE to be executed and argCount is the number of VALUEs in the args array.
\par 
\par Contrived example:
\par 
\par }{\f2\cgrid0 VALUE myCoreFunc(VALUE* self, int argCount, VALUE args[], 
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cgrid0 CInterpreter* pInterp)
\par }\pard \nowidctlpar\adjustright {\f2\cgrid0 \{
\par \tab ASSERT_SELF(_T("testmod"));
\par \tab VALUE vFunc = self->Get(_T("add2"));
\par \tab ASSERT(vFunc.Type() == stFunction); // Make sure it exists
\par \tab int c = 2;
\par \tab VALUE result, a[2] = \{23, 65\};
\par \tab if (!pInterp->ExecFuncFromCore(result, vFunc, c, a, self)) \{
\par \tab \tab return AllocNil();  // Exception occured
\par \tab \}
\par \tab ASSERT(result.Integer() == 88);
\par \tab return result;
\par \}}{\cgrid0 
\par 
\par Two final notes:  Do not use }{\f2\cgrid0 CInterpreter::Execute()}{\cgrid0  to execute a function while the interpreter is running since that function messes with exceptions states (among other things).  }{\f2\cgrid0 CInterpreter::ExecFuncFromCore()}{
\cgrid0  differs from }{\f2\cgrid0 CInterpreter::ExecFunc()}{\cgrid0  in that it copies the necessary fields needed to maintain the reference to the parent class into the function before executing.
\par 
\par 
\par 
\par }{\b\fs24\cf6\cgrid0 Quick info on properties and property functions...
\par }{\cgrid0 
\par Create a property using AllocProperty().  Both the read and write functions are regular stFunction core functions created using AllocFunction().
\par A property does not have to be a Dialect class member.
\par 
\par The }{\cf2\cgrid0 read function}{\cgrid0  will always receive exactly on argument (ie. argCount == 1) whose value is the current value of
\par the property.  The value returned by the readFunc is the value which the outside world sees as the property value.
\par Normally, you would return the argument received in args[0], since that is the current value.  DO NOT return an stUndefined VALUE.  If nothing is appropriate use: return AllocNil();
\par 
\par The }{\cf2\cgrid0 write function }{\cgrid0 always received exactly t
wo arguments.  The first argument is the current value of the property and the second argument is the value attempting to become the new value of the property.  The property value will be set to whatever value the writeFunc returns.  DO NOT return an stUn
defined VALUE in the write function.
\par 
\par To get direct access to the properties current value from within C++ use something like:
\par }{\f2\cgrid0 if (vProp.Type() == stProperty) \{
\par \tab VALUE val = vProp.Property()->m_PropValue;
\par \}
\par }{\cgrid0 
\par 
\par }{\b\fs24\cf6\cgrid0 Quick info on using stVoid VALUEs...
\par }{\cgrid0 
\par Create a stVoid VALUE using the following function:
\par }{\f2\cf2\cgrid0 
\par VALUE AllocVoid(void* ptr, void (*freeVoid)(void* pVoid), TString rttiName);
\par }{\cf1\cgrid0 
\par Where ptr is the pointer to be stored, freeVoid is the function to be called when it is time for the VALUE to be garbage collected, and rttiName is the name to be returned by }{\f2\cf1\cgrid0 VALUE::TypeString()}{\cf1\cgrid0  or compared to when calling }
{\f2\cf1\cgrid0 VALUE::Isa()}{\cf1\cgrid0 .  The rttiName should generally by the name 
of the class pointed to by ptr.  If NULL is used as the freeVoid argument, then when the VALUE is garaged collected the pointer will simply be "let go".  This is only desirable if the pointer was not dynamically allocated, or if the pointer will be cleane
d up somewhere else in the program.
\par 
\par Here is an example freeVoid function:
\par 
\par }{\f2\cgrid0 void freeMyClass(void* pVoid) \{
\par \tab if (pVoid != NULL) \{
\par \tab \tab CMyClass* v = (CMyClass*)pVoid;
\par \tab \tab v->FinishUp();
\par \tab \tab delete v;
\par \tab \}
\par \}
\par }{\cf1\cgrid0 
\par 
\par }{\b\fs24\cf6\cgrid0 Quick info on setting background handlers (e.g. message pumps)...}{\cgrid0 
\par 
\par If set, the interpreter calls the background handler during execution to allow the owner to do whatever processing needs to be done.  For example, in a GUI operating system, message pumping
 might need to be done to allow painting, or user interface features to function.  This is similar to the breakpoint function, except that instead of being called at every line, it is called at a specified interval in order to reduce the function overhead
.  It is possible to use the breakpoint function in a similar manor to the background handler, although it is more efficient to use the background handler with a frequency of 100 of more (if you aren't also checking for breakpoints).
\par 
\par To set the background handler:\line \line }{\f2\cf2\cgrid0 void CInterpreter::SetBackgroundHandler(int frequency, 
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cf2\cgrid0 void (*backgroundFunc)(CInterpreter* pInterp));\line }{\cgrid0 
\par }\pard \nowidctlpar\adjustright {\cgrid0 Where frequency determines how often the handler is called.  A frequency of one is roughly equivalent to one call to the handler for each lin
e of code executed. Setting it to one would be definite overkill.  In most cases, I'm sure frequencies of 100+ would be more than adequate.\line \line If the background function needs to know what module and line is currently about to execute, call }{
\f2\cgrid0 CInterpreter::CurrentModule()}{\cgrid0  and }{\f2\cgrid0 CInterpreter::CurrentLine()}{\cgrid0 .\line \line Under Windows, the background handler MUST call }{\f2\cgrid0 pInterp->Abort()}{\cgrid0 
 if it receives a WM_QUIT message or else the interpreter won't realize it needs to stop. In most cases it eventually would, but in some cases (infinite loops come to mind), the app would never actually die.\line 
\par }{\f2\cgrid0 // Sample background handler
\par void BackgroundHandler(CInterpreter* pInterp)
\par \{
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cgrid0 MSG msg;
\par while (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) \{
\par }\pard \fi720\li720\nowidctlpar\adjustright {\f2\cgrid0 if (!AfxGetApp->PumpMessage()) \{
\par }\pard \fi720\li1440\nowidctlpar\adjustright {\f2\cgrid0 ::PostQuitMessage(0);
\par pInterp->Abort(); // Tell interp to stop
\par return;
\par }\pard \fi720\li720\nowidctlpar\adjustright {\f2\cgrid0 \}
\par }\pard \li720\nowidctlpar\adjustright {\f2\cgrid0 \}\line // let MFC do its idle processing
\par LONG lIdle = 0;
\par while (AfxGetApp()->OnIdle(lIdle++));
\par }\pard \nowidctlpar\adjustright {\f2\cgrid0 \}\line 
\par }{\cgrid0 
\par 
\par }{\b\fs24\cf6\cgrid0 Quick info on setting the breakpoint function...}{\cgrid0 
\par 
\par The breakpoint function, if set, is called before each line of a module is executed.  Execution resumes when the breakpoint function returns.
\par 
\par }{\f2\cf2\cgrid0 void CInterpreter::SetBreakPointFunc(bool (*breakPointFunc)
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cf2\cgrid0 (TString module, int line, CInterpreter* pInterp));
\par }\pard \nowidctlpar\adjustright {\cgrid0 \line 
\par }{\b\fs24\cf6\cgrid0 Quick info on debugging functions...}{\cgrid0 
\par 
\par When inside the breakpoint function (or the background handler), it is possible to access the interpreter's state using the following debug functions.  Do not use the normal InterpretText and Evaluate functions because they c
hange the execution environment, whereas these debug functions do not.
\par 
\par }{\f2\cf2\cgrid0 bool CInterpreter::DebugInterpretText(LPCTSTR text, TString& exceptionText);
\par }{\cgrid0 \line Similar to }{\f2\cgrid0 CInterpreter::InterpretText()}{\cgrid0 .  Interprets the given text. If an exception occurs during execut
ion, it returns false and sets exceptionText to the exception message text. Returns true if no exception occurs.
\par \line }{\f2\cf2\cgrid0 bool CInterpreter::DebugEvaluate(LPCTSTR text, VALUE& result,
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cf2\cgrid0 TString& exceptionText);}{\cgrid0 
\par }\pard \nowidctlpar\adjustright {\cgrid0 \line Similar to }{\f2\cgrid0 CInterpreter::Evaluate()}{\cgrid0 .  The text to be e
valuated must be a legal RHS expression. The result of the expression is returned in vResult.  If no exceptions occur, the function returns true. Otherwise it returns\line 
false and sets exceptionText to the exception message. This is the perfect function to use for a watch window.\line 
\par }{\f2\cgrid0 TString text;
\par VALUE vResult;
\par if (pInterp->DebugEvaluate(_T("myVar"), vResult, text)) \{
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cgrid0 text.Format("myVar => %s", vResult.String());
\par }\pard \nowidctlpar\adjustright {\f2\cgrid0 \}
\par else \{
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cgrid0 // text currently contains an exception message, but
\par // we know it just says myVar is undefined (due to the
\par // simplicity of the expression we were evaluating)
\par text = "myVar => Not bound in current context";
\par }\pard \nowidctlpar\adjustright {\f2\cgrid0 \}\line }{\cgrid0 
\par If you want to see what the return value of a function is, there is a variable local to the function called __result__ which is set by the return statement (although there's nothing the says you can't just assign to it directly).\line \line So:\line }{
\f2\cgrid0 if (pInterp->DebugEvaluate("__result__", vResult, text)) \{
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cgrid0 text.Format("__result__ => %s", vResult.String());\line \}\line }{\cgrid0 
\par }\pard \nowidctlpar\adjustright {\cgrid0 
\par 
\par 
\par }{\b\fs24\cf6\cgrid0 Quick info on setting module search paths...}{\cf1\cgrid0 
\par }{\cgrid0 
\par When importing a module, Dialect first looks for the module in the interpreter's directory.  If not found, then the search will continue in each directory specified in a path string.  You can set the initial path 
(before starting script execution) by calling the following C++ function:
\par \line }{\f2\cf2\cgrid0 void CInterpreter::SetInitialPath(TString path);}{\cgrid0 \line 
\par To set the path from inside a Dialect script, do something like:
\par \line }{\f2\cf2\cgrid0 global.__environ__.path = "C:\\aristar\\scripts;C:\\aristar\\misc"}{\cgrid0 
\par 
\par 
\par 
\par 
\par }{\f2\cgrid0 
\par \page /////////////////////////////////////////////////////////
\par /////////////////////////////////////////////////////////
\par // Example of overridding RequestModule()...
\par 
\par VALUE CXInterpreter::RequestModule(TString moduleName)
\par \{
\par \tab if (moduleName.CompareNoCase(_T("testmod")) == 0) \{
\par \tab \tab return BuildTestmod();
\par \tab \}
\par \tab // Always call the base class version of RequestModule()
\par \tab // since it also checks for modules registered with the
\par \tab // CInterpreter::RegisterCoreModule function
\par \tab return CInterpreter::RequestModule(moduleName);
\par \}
\par 
\par /////////////////////////////////////////////////////////
\par /////////////////////////////////////////////////////////
\par // Example of using RegisterModule() outside interpreter
\par 
\par CInterpreter interp;
\par interp.RegisterCoreModule(_T("testmod"), BuildTestmod);
\par 
\par /////////////////////////////////////////////////////////
\par /////////////////////////////////////////////////////////
\par // Example of using RegisterCoreModule() inside interpreter
\par 
\par CXInterpreter::CXInterpreter()
\par \{
\par \tab // Contructor is a good place to register...
\par \tab RegisterCoreModule(_T("testmod"), BuildTestmod);
\par \}
\par 
\par 
\par /////////////////////////////////////////////////////////
\par /////////////////////////////////////////////////////////
\par // Example of a function to build a module...
\par //\tab If used in call to RegisterCoreModule(), the function must
\par //\tab be a regular function or a static class member function.
\par //\tab If used in the virtual override of RequestModule, it
\par //\tab doesn't matter, since we aren't storing a pointer to it.
\par 
\par VALUE BuildTestmod()
\par \{
\par \tab // Create the module frame and store it's name
\par \tab VALUE module = AllocFrame(31);
\par \tab module.Set(_T("__module__"), _T("testmod"));
\par 
\par \tab // Create a new class for the module...
\par \tab VALUE myClass = AllocClass(_T("myclass"), VALUE());
\par \tab module.Set(_T("myclass"), myClass);
\par 
\par \tab // Add some class member functions, data, etc. to myClass
\par \tab myClass.Set(_T("__init__"), AllocFunc(myClassInit, _T("testmod")));
\par \tab myClass.Set(_T("sharedInt"), 5);
\par \tab // Add a property.  The readFunc is set to nil so it instantly
\par \tab // returns it's current value with no questions asked...
\par \tab myClass.Set(_T("visible"), AllocProperty(AllocNil(), 
\par \tab \tab AllocFunction(writeVisible, _T("testmod")), false));
\par \tab 
\par \tab // Add some other data to the module...
\par \tab module.Set(_T("meaningOfLife"), 42);
\par 
\par \tab return module;
\par \}
\par 
\par VALUE myClassInit(VALUE* self, int argCount, VALUE args[], CInterpreter* pInterp)
\par \{
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cgrid0 ASSERT_SELF(_T("testmod"));
\par }\pard \nowidctlpar\adjustright {\f2\cgrid0 
\par \tab // Let's say we want one contructor integer argument which
\par \tab // will be called "numTrees" and one string argument which
\par \tab // will be called "treeType".
\par \tab // Accessed in Dialect like:
\par \tab //\tab \tab myClassInst = myClass(34, "redwood")
\par \tab //\tab \tab x = myClassInst.numTrees
\par \tab //\tab \tab y = myClassInst.treeType
\par 
\par \tab // We will use ValidateArgs(), though we could do it manually
\par \tab // if we need a little more control over arg validation...
\par \tab SymbolTypes types[] = \{stInteger, stString\};
\par \tab if (ValidateArgs(2, types, argCount, args, pInterp)) \{
\par \tab \tab self->Set(_T("numTrees"), args[0]);
\par \tab \tab self->Set(_T("treeType"), args[1]);
\par \tab \}
\par 
\par \tab // The return value of a class contructor is actually ignored...
\par \tab return AllocNil();
\par \}
\par 
\par VALUE writeVisible(VALUE* self, int argCount, VALUE args[], CInterpreter* pInterp)
\par \{
\par }\pard \fi720\nowidctlpar\adjustright {\f2\cgrid0 ASSERT_SELF(_T("testmod"));
\par }\pard \nowidctlpar\adjustright {\f2\cgrid0 \tab ASSERT(argCount == 2);
\par \tab 
\par \tab bool currentValue = args[0].Boolean();
\par 
\par \tab if (args[1].Type() != stBoolean) \{
\par \tab \tab pInterp->RuntimeError(
\par \tab \tab \tab _T("Visible property expects a boolean value"));
\par \tab \tab return currentValue;\tab // We'll keep the original value
\par \tab \}
\par \tab else if (args[1].Boolean() == currentValue) \{
\par \tab \tab // The value is the same, nothing to do but return...
\par \tab \tab return args[1];
\par \tab \}
\par \tab else \{
\par \tab \tab // We need to take action!
\par \tab \tab CMyObject* p = (CMyObject*)self->Get(_T("#myVoidObj")).Void();
\par \tab \tab p->SetVisible(args[1].Boolean());
\par \tab \tab return args[1];
\par \tab \}
\par \}
\par }}